package trackexplorer.editors;

import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;

import org.apache.tools.tar.TarEntry;
import org.apache.tools.tar.TarInputStream;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IViewReference;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.EditorPart;
import org.eclipse.ui.texteditor.StatusLineContributionItem;

import trackexplorer.Calibration;
import trackexplorer.views.EditDirsView;
import trackexplorer.views.ShowView;
import trackexplorer.views.TrackView;

public class MapViewer extends EditorPart{

    public static final String ID        = "trackexplorer.editors.MapViewer";
    private Image              mapImage;
    private Calibration        calibration;
    private Set<String>        tracks;
    private int                cur_x     = 0;
    private int                cur_y     = 0;
    private Shell              shell;
    private Canvas             canvas;
    private ScrolledComposite  sc;
    private Image              trImage;
    private Image              rteImage;
    private Image              img;
    private Point              imgP;
    private boolean            showRoute = true;

    @Override
    public void dispose(){
        super.dispose();
        if(mapImage != null && !mapImage.isDisposed()){
            mapImage.dispose();
            mapImage = null;
        }
        if(trImage != null && !trImage.isDisposed()){
            trImage.dispose();
            trImage = null;
        }
        if(img != null && !img.isDisposed()){
            img.dispose();
            img = null;
        }
    }

    public MapViewer(){
        calibration = new Calibration();
        tracks = new HashSet<String>();
        imgP = new Point(-50, -50);
    }

    @Override
    public void createPartControl(Composite parent){
        shell = parent.getShell();
        sc = new ScrolledComposite(parent, SWT.H_SCROLL | SWT.V_SCROLL);
        canvas = new Canvas(sc, SWT.NONE);
        sc.setContent(canvas);
        sc.getVerticalBar()
            .setIncrement(sc.getVerticalBar().getIncrement() * 5);
        MapInput mapInput = (MapInput)getEditorInput();
        final File map = mapInput.getFile();
        IRunnableWithProgress op = new IRunnableWithProgress(){
            public void run(IProgressMonitor monitor)
                throws InvocationTargetException, InterruptedException{
                try{
                    mapImage = drawMap(map);
                }catch(IOException e){
                    throw new InterruptedException();
                }
                clearTrImage();
            }
        };
        try{

            new ProgressMonitorDialog(PlatformUI.getWorkbench()
                .getActiveWorkbenchWindow().getShell()).run(true, true, op);
        }catch(Exception e){
            e.printStackTrace();
        }
        canvas.setSize(calibration.getSize());
        canvas.setCursor(new Cursor(parent.getDisplay(), SWT.CURSOR_CROSS));
        canvas.addPaintListener(new PaintListener(){
            public void paintControl(PaintEvent e){
                e.gc.drawImage(mapImage, 0, 0);
                if(trImage != null){
                    e.gc.drawImage(trImage, 0, 0);
                }
                if(rteImage != null && showRoute){
                    e.gc.drawImage(rteImage, 0, 0);
                }
                if(img != null){
                    e.gc.drawImage(img, imgP.x, imgP.y);
                }
            }
        });
        canvas.addMouseMoveListener(new MouseMoveListener(){
            public void mouseMove(MouseEvent e){
                if(cur_x != 0 || cur_y != 0){
                    sc.setOrigin(sc.getOrigin().x + cur_x - e.x,
                        sc.getOrigin().y + cur_y - e.y);
                    miniNotify();
                }
                IStatusLineManager slMgr = getEditorSite().getActionBars()
                    .getStatusLineManager();
                StatusLineContributionItem geoItem = (StatusLineContributionItem)slMgr
                    .find("geoPosItem");
                StatusLineContributionItem pixItem = (StatusLineContributionItem)slMgr
                    .find("pixPosItem");
                double[] geo = calibration.pix2geo(e.x, e.y);
                String s = String.format("%15.8f, %15.8f", geo[1], geo[0]);
                geoItem.setText(s);
                pixItem.setText(e.x + ", " + e.y);
            }
        });
        canvas.addMouseListener(new MouseListener(){
            public void mouseDoubleClick(MouseEvent e){
                EditDirsView ed = (EditDirsView)PlatformUI.getWorkbench()
                    .getActiveWorkbenchWindow().getActivePage()
                    .findViewReference(EditDirsView.ID).getView(false);
                if(ed == null){
                    return;
                }
                double[] geo = calibration.pix2geo(e.x, e.y);
                ed.dblClickNotify(MapViewer.this, geo[1], geo[0]);
            }

            public void mouseDown(MouseEvent e){
                if(e.button == 1){
                    cur_x = e.x;
                    cur_y = e.y;
                }
            }

            public void mouseUp(MouseEvent e){
                cur_x = 0;
                cur_y = 0;
                if(e.button == 2){
                    EditDirsView ed = (EditDirsView)PlatformUI.getWorkbench()
                        .getActiveWorkbenchWindow().getActivePage()
                        .findViewReference(EditDirsView.ID).getView(false);
                    if(ed == null){
                        return;
                    }
                    double[] geo = calibration.pix2geo(e.x, e.y);
                    ed.selectEntry(geo[1], geo[0]);
                }else if(e.button == 3){
                    EditDirsView ed = (EditDirsView)PlatformUI.getWorkbench()
                        .getActiveWorkbenchWindow().getActivePage()
                        .findViewReference(EditDirsView.ID).getView(false);
                    if(ed == null){
                        return;
                    }
                    double[] geo = calibration.pix2geo(e.x, e.y);
                    ed.clickNotify(MapViewer.this, geo[1], geo[0]);
                }
            }
        });

        sc.getHorizontalBar().addSelectionListener(new SelectionListener(){
            public void widgetDefaultSelected(SelectionEvent e){}

            public void widgetSelected(SelectionEvent e){
                miniNotify();
            }
        });

        sc.getVerticalBar().addSelectionListener(new SelectionListener(){
            public void widgetDefaultSelected(SelectionEvent e){}

            public void widgetSelected(SelectionEvent e){
                miniNotify();
            }
        });
        IViewReference edref = PlatformUI.getWorkbench()
            .getActiveWorkbenchWindow().getActivePage().findViewReference(
                EditDirsView.ID);
        if(edref != null){
            EditDirsView ed = (EditDirsView)edref.getView(false);
            if(ed != null){
                ed.clickNotify(this, 0, 0);
            }
        }
    }

    private Image drawMap(File map) throws IOException{
        if(map.getName().toLowerCase().endsWith(".tar")){
            return drawTarMap(map);
        }
        calibration.readMap(new FileInputStream(map));
        Point size = calibration.getSize();
        int width = size.x;
        int height = size.y;
        File dir = new File(map.getParentFile(), "set");
        if(!dir.exists()){
            return getOziImage(map);
        }
        String[] files = dir.list(new FilenameFilter(){
            public boolean accept(File dir, String name){
                return Calibration.isGraphics(name);
            }
        });
        Image retMap = new Image(shell.getDisplay(), width, height);
        GC gc = new GC(retMap);
        for(int i = 0; i < files.length; i++){
            String[] s = files[i].split("\\.");
            s = s[s.length - 2].split("_");
            String tmp = "";
            for(int j = 0; j < s.length - 2; j++){
                tmp += s[j];
            }
            int x, y;
            x = Integer.parseInt(s[s.length - 2]);
            y = Integer.parseInt(s[s.length - 1]);

            FileInputStream fis = new FileInputStream(new File(dir, files[i]));
            Image img = new Image(shell.getDisplay(), fis);
            gc.drawImage(img, x, y);
            img.dispose();
            fis.close();
            img = null;
        }
        gc.dispose();
        return retMap;
    }

    private Image getOziImage(File map) throws IOException{
        String name = map.getName().toLowerCase();
        final String na = name.substring(0, name.length() - 4);
        File[] files = map.getParentFile().listFiles(new FilenameFilter(){
            public boolean accept(File dir, String name){
                return name.toLowerCase().substring(0, name.length() - 4)
                    .equals(na)
                    && Calibration.isGraphics(name);
            }
        });
        if(files == null || files.length < 1){
            return null;
        }
        return new Image(shell.getDisplay(), new FileInputStream(files[0]));
    }

    private Image drawTarMap(File tar) throws IOException{
        TarInputStream file = new TarInputStream(new FileInputStream(tar));
        TarEntry entry;
        PipedOutputStream os = new PipedOutputStream();
        while((entry = file.getNextEntry()) != null){
            if(entry.getName().toLowerCase().endsWith(".map")){
                PipedInputStream is = new PipedInputStream(os);
                file.copyEntryContents(os);
                calibration.readMap(is);
                is.close();
                os.close();
                file.close();
                break;
            }
        }
        Point size = calibration.getSize();
        int width = size.x;
        int height = size.y;

        Image retMap = new Image(shell.getDisplay(), width, height);
        GC gc = new GC(retMap);
        file = new TarInputStream(new FileInputStream(tar));
        while((entry = file.getNextEntry()) != null){
            String name = entry.getName().toLowerCase();
            if(!(name.startsWith("set/") && Calibration.isGraphics(name))){
                continue;
            }
            os = new PipedOutputStream();
            PipedInputStream is = new PipedInputStream(os);//XXX, (int)entry.getSize());
            file.copyEntryContents(os);
            String[] s = name.split("\\.");
            s = s[s.length - 2].split("_");
            String tmp = "";
            for(int j = 0; j < s.length - 2; j++){
                tmp += s[j];
            }
            int x, y;
            x = Integer.parseInt(s[s.length - 2]);
            y = Integer.parseInt(s[s.length - 1]);

            Image img = new Image(shell.getDisplay(), is);
            gc.drawImage(img, x, y);

            img.dispose();
            img = null;
            is.close();
            os.close();
        }
        gc.dispose();
        return retMap;
    }
    private int MINISIZE = 300;

    private void setMini(){
        ShowView sv = (ShowView)PlatformUI.getWorkbench()
            .getActiveWorkbenchWindow().getActivePage().findViewReference(
                ShowView.ID).getView(false);
        if(sv == null){
            return;
        }
        Rectangle bounds = mapImage.getBounds();
        double scale = Math.max(bounds.width, bounds.height);
        scale /= MINISIZE;
        Image img = new Image(null, MINISIZE, MINISIZE);
        GC gc = new GC(img);
        gc.setInterpolation(SWT.HIGH);
        int w = (int)(bounds.width / scale);
        int h = (int)(bounds.height / scale);
        gc.drawImage(mapImage, 0, 0, bounds.width, bounds.height, 0, 0, w, h);
        gc.dispose();
        sv.setMiniMap(img, scale);
        miniNotify();
    }

    public void miniNotify(){
        ShowView sv = (ShowView)PlatformUI.getWorkbench()
            .getActiveWorkbenchWindow().getActivePage().findViewReference(
                ShowView.ID).getView(false);
        sv.mapPartListener(sc.getOrigin().x, sc.getOrigin().y, sc.getSize().x,
            sc.getSize().y);
    }

    public Calibration getCalibration(){
        return calibration;
    }

    public void drawPoint(int[] p){
        if(img != null && !img.isDisposed()){
            img.dispose();
            img = null;
        }
        if(p == null){
            canvas.redraw();
            return;
        }
        img = new Image(shell.getDisplay(), 40, 40);
        imgP.x = p[0] - 20;
        imgP.y = p[1] - 20;
        ImageData id = img.getImageData();
        id.transparentPixel = -256;
        Image tmp = new Image(shell.getDisplay(), id);
        if(img != null && !img.isDisposed()){
            img.dispose();
            img = null;
        }
        img = tmp;
        GC gc = new GC(img);
        gc.setForeground(new Color(null, 255, 0, 0));
        gc.setLineWidth(2);
        drawWPoint(gc, new int[]{20, 20});
        gc.dispose();
        showPoint(p[0], p[1]);
    }

    public void showPoint(int x, int y){
        Point size = sc.getSize();
        sc.setOrigin(x - size.x / 2, y - size.y / 2);
        canvas.redraw();
    }

    private void drawWPoint(GC gc, int[] x){
        gc.drawOval(x[0] - 10, x[1] - 10, 20, 20);
        gc.drawLine(x[0], x[1] - 15, x[0], x[1] + 15);
        gc.drawLine(x[0] - 15, x[1], x[0] + 15, x[1]);
    }

    public boolean addTrack(File track){
        RGB[] colors = Calibration.colors;
        Map<int[], String> gpx = calibration.readGpx(track);
        int[] r = new LinkedList<int[]>(gpx.keySet()).getLast();
        Rectangle rec = new Rectangle(r[0], r[1], r[2] - r[0], r[3] - r[1]);
        if(!new Rectangle(0, 0, calibration.getSize().x,
            calibration.getSize().y).intersects(rec)){
            return false;
        }
        int i = -1;
        GC gc = new GC(trImage);
        for(int[] points: gpx.keySet()){
            i++;
            i %= colors.length;
            if(gpx.get(points).equals("wpt")){
                gc.setForeground(new Color(null, 255, 0, 0));
                gc.setLineWidth(1);
                drawWPoint(gc, points);
            }else if(gpx.get(points).equals("trk")
                || gpx.get(points).equals("rte")){
                gc.setLineWidth(2);
                gc.setForeground(new Color(null, colors[i]));
                gc.drawPolyline(points);
            }
        }
        tracks.add(track.getAbsolutePath());
        canvas.redraw();
        gc.dispose();
        return true;
    }

    public void removeTrack(String track){
        tracks.remove(track);
        clearTrImage();
        for(String t: tracks){
            addTrack(new File(t));
        }
        canvas.redraw();
    }

    private void clearTrImage(){
        if(trImage != null && !trImage.isDisposed()){
            trImage.dispose();
            trImage = null;
        }
        Rectangle bounds = mapImage.getBounds();
        trImage = new Image(shell.getDisplay(), bounds.width, bounds.height);
        ImageData id = trImage.getImageData();
        id.transparentPixel = -256;
        Image tmp = new Image(shell.getDisplay(), id);
        if(trImage != null && !trImage.isDisposed()){
            trImage.dispose();
            trImage = null;
        }
        trImage = tmp;
    }

    @Override
    public void setFocus(){
        setMini();
        canvas.setFocus();
        TrackView tv = (TrackView)PlatformUI.getWorkbench()
            .getActiveWorkbenchWindow().getActivePage().findViewReference(
                TrackView.ID).getView(false);
        if(tv != null){
            tv.checkTracks(tracks.toArray());
        }
        IViewReference edref = PlatformUI.getWorkbench()
            .getActiveWorkbenchWindow().getActivePage().findViewReference(
                EditDirsView.ID);
        if(edref != null){
            EditDirsView ed = (EditDirsView)edref.getView(false);
            if(ed != null){
                ed.clickNotify(this, 0, 0);
            }
        }
        IStatusLineManager slMgr = getEditorSite().getActionBars()
            .getStatusLineManager();
        StatusLineContributionItem imgDataItem = (StatusLineContributionItem)slMgr
            .find("imgDataItem");
        ImageData data = mapImage.getImageData();
        int w = data.width;
        int h = data.height;
        int d = data.depth;
        String s = String.format("%dx%d, %dBPP", w, h, d);
        imgDataItem.setText(s);
    }

    public void showRoute(){
        showRoute = !showRoute;
        canvas.redraw();
    }

    public void setRoute(double[] points){
        if(rteImage != null && !rteImage.isDisposed()){
            rteImage.dispose();
            rteImage = null;
        }
        if(points == null){
            return;
        }
        int[] path = new int[points.length];
        for(int i = 0; i < points.length; i += 2){
            int[] pix = calibration.geo2pix(points[i], points[i + 1]);
            path[i] = pix[0];
            path[i + 1] = pix[1];
        }
        Rectangle bounds = mapImage.getBounds();
        rteImage = new Image(shell.getDisplay(), bounds.width, bounds.height);
        ImageData id = rteImage.getImageData();
        id.transparentPixel = -256;
        Image tmp = new Image(shell.getDisplay(), id);
        GC gc = new GC(tmp);
        gc.setForeground(new Color(shell.getDisplay(), 255, 0, 0));
        gc.setLineWidth(2);
        gc.drawPolyline(path);
        gc.dispose();
        rteImage.dispose();
        rteImage = null;
        rteImage = tmp;
    }

    @Override
    public void doSave(IProgressMonitor monitor){}

    @Override
    public void doSaveAs(){}

    @Override
    public void init(IEditorSite site, IEditorInput input)
        throws PartInitException{
        setSite(site);
        setInput(input);
        setPartName(input.getName());
    }

    @Override
    public boolean isDirty(){
        return false;
    }

    @Override
    public boolean isSaveAsAllowed(){
        return false;
    }

    public void setShowRect(Point p){
        sc.setOrigin(p);
        miniNotify();
    }

    public ImageData getImageData(){
        return mapImage.getImageData();
    }

    public Image getMapImage(){
        return mapImage;
    }

    public double[] getCenter(){
        Point o = sc.getOrigin();
        Point s = sc.getSize();
        double[] geo = calibration.pix2geo(o.x + s.x / 2, o.y + s.y / 2);
        return geo;
    }
}
